package com.roamer.school.shiro.config;

import com.roamer.school.common.Constants;
import com.roamer.school.shiro.util.CodecUtils;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro 配置
 *
 * @author roamer
 * @version V1.0
 * @date 2018/12/26 19:51
 */
@Configuration
public class ShiroConfig {


    /**
     * 设置ShiroFilter过滤规则
     * <ul>
     * <li>anon:所有url都都可以匿名访问</li>
     * <li>authc:所有url都必须认证通过才可以访问</li>
     * <p>
     * <p>
     * </ul>
     *
     * @param securityManager SecurityManager
     *
     * @return {@code ShiroFilterFactoryBean}
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 过滤链定义，从上向下顺序执行，一般将/**放在最为下边
        Map<String, String> filterMap = new LinkedHashMap<>();

        // 配置不会过滤的资源
        filterMap.put("/captcha", "anon");
        filterMap.put("/static/**", "anon");
        // 放行 swagger
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/v2/api-docs/**", "anon");
        filterMap.put("/webjars/springfox-swagger-ui/**", "anon");
        // 放行 h2 console
        filterMap.put("/console/**", "anon");
        // 放行 自定义login路径
        filterMap.put("/login", "anon");

        // 其他需要拦截的路径
        filterMap.put("/**", "authc");
        // 如果不设置默认"/login"
        shiroFilterFactoryBean.setLoginUrl("/unLogin");
        // 配置logout过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterMap.put("/logout", "logout");
        // 未授权界面
        //        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * SecurityManager
     * Shiro核心,协调多个组件进行认证和鉴权
     *
     * @return {@code SecurityManager}
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 自定义Realm,提供认证和鉴权数据
     * <p>
     * 需要设置到SecurityManager中
     * </p>
     *
     * @return {@code MyShiroRealm} 自定义Realm
     */
    @Bean
    public Realm myShiroRealm() {
        MyShiroRealm realm = new MyShiroRealm();
        realm.setCredentialsMatcher(credentialsMatcher());
        return realm;
    }

    /**
     * 认证匹配器
     * <p>
     * 需要设置到Realm中
     * </p>
     *
     * @return {@code CredentialsMatcher} 认证匹配器
     */
    @Bean
    public CredentialsMatcher credentialsMatcher() {
        // 设置加密算法
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(
                CodecUtils.DEFAULT_HASH_ALGORITHM_NAME);
        // 加密次数
        credentialsMatcher.setHashIterations(CodecUtils.DEFAULT_HASH_ITERATIONS);
        return credentialsMatcher;
    }


    /**
     * 生成RememberMe管理器
     * <p>
     * 需要设置到SecurityManager中
     * </p>
     *
     * @return {@code CookieRememberMeManager}
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // Cookie加密的密钥,默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvBhpFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

    /**
     * 设置Cookie的生成模版，比如cookie的name，cookie的有效时间等等
     * <p>
     * 需要设置到CookieRememberMeManager中
     * </p>
     *
     * @return {@code SimpleCookie}
     */
    @Bean
    public SimpleCookie rememberMeCookie() {
        // Cookie名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie(Constants.ME_COOKIE_NAME);
        // 记住我cookie生效时间 ,单位秒;
        simpleCookie.setMaxAge(Constants.ME_COOKIE_EXPIRY_TIME);
        simpleCookie.setHttpOnly(true);
        return simpleCookie;
    }

    /**
     * Shiro生命周期处理器
     * <p>
     * LifecycleBeanPostProcessor在其内部自动分别调用了
     * {@link org.apache.shiro.util.Initializable}和{@link org.apache.shiro.util.Destroyable}实现类的
     * {@code init()}和{@code destroy()},从而达到管理shiro bean生命周期的目的
     * </p>
     *
     * @return {@code LifecycleBeanPostProcessor}
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * shiro自动代理
     * <p>
     * DefaultAdvisorAutoProxyCreator是用来扫描上下文,寻找所有的Advisor(通知器),
     * 将这些Advisor应用到所有符合切入点的Bean中。
     * 所以必须在lifecycleBeanPostProcessor创建之后创建
     * </p>
     *
     * @return {@code DefaultAdvisorAutoProxyCreator}
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // shiro starter 默认实现未设置此属性，会导致开启事务的Service无法注入，因此替换默认设置
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
     * <p>
     * 匹配所有类
     * 匹配所有加认证注解的方法
     * </p>
     *
     * @param securityManager shiro安全管理器
     *
     * @return {@code AuthorizationAttributeSourceAdvisor}
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}
